package org.sean.framework.web.config;

import io.micrometer.core.instrument.MeterRegistry;
import io.swagger.v3.oas.annotations.ExternalDocumentation;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.sean.framework.auth.service.CaptchaService;
import org.sean.framework.auth.token.JWT;
import org.sean.framework.auth.token.JWTProperty;
import org.sean.framework.context.ApplicationInfo;
import org.sean.framework.context.SpringApplicationContext;
import org.sean.framework.redis.RedisService;
import org.sean.framework.redis.StringRedisTemplate;
import org.sean.framework.util.DateUtil;
import org.sean.framework.web.converter.DefaultResponseBodyConverter;
import org.sean.framework.web.converter.GsonHttpMessageConverter;
import org.sean.framework.web.endpoint.StatusInfoEndpoint;
import org.sean.framework.web.filter.HeaderFilter;
import org.sean.framework.web.filter.LogFilter;
import org.sean.framework.web.filter.MutableHeaderCallback;
import org.sean.framework.web.filter.MutableHeaderFilter;
import org.sean.framework.web.filter.XssFilter;
import org.sean.framework.web.interceptor.ApiInterceptor;
import org.sean.framework.web.service.FreeMarkerService;
import org.sean.framework.web.tools.ApiFinder;
import org.sean.framework.web.tools.ApiPoster;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerConfigurationFactory;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import java.util.Set;
import java.util.stream.Collectors;

/**
 * WEB 配置
 *
 * @author xielei
 */
@Configuration
public class WebConfiguration {


    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @Import({RequestBodyAdviceHandler.class, ResponseBodyAdviceHandler.class, StatusInfoEndpoint.class})
    static class ServletConfig {

    }

    @OpenAPIDefinition(
            info = @Info(
                    title = "接口文档"
            ),
            externalDocs = @ExternalDocumentation(description = "参考文档",
                    url = "https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations"
            )
    )
    @Configuration
    static class MeterRegistryConfig {
        @Value("${spring.application.name:APP_NAME}")
        private String applicationName;

        @Bean
        @ConfigurationProperties(prefix = "app")
        public ApplicationInfo applicationInfo() {
            return new ApplicationInfo();
        }

        @Bean
        MeterRegistryCustomizer<MeterRegistry> appMetricsCommonTags() {
            return registry -> registry.config().commonTags("application", applicationName);
        }
    }

    @Configuration
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @ConditionalOnClass({freemarker.template.Configuration.class, FreeMarkerConfigurationFactory.class})
    static class FreeMarkerConfiguration {

        @Bean
        public FreeMarkerService freeMarkerService(FreeMarkerConfigurer freeMarkerConfigurer) {
            return new FreeMarkerService(freeMarkerConfigurer);
        }
    }

    @Configuration
    static class CaptchaConfiguration {
        @Bean
        @Primary
        public CaptchaService captchaService() {
            return new CaptchaService();
        }
    }

    @ConditionalOnProperty(prefix = "app.jwt", name = "secretKey")
    static class JWTConfiguration {
        @Bean
        @ConfigurationProperties(prefix = "app.jwt")
        public JWTProperty jwtProperties() {
            return new JWTProperty();
        }

    }

    @ConditionalOnBean({JWTProperty.class, StringRedisTemplate.class})
    @ConditionalOnClass(RedisOperations.class)
    static class ClockConfiguration {

        @Bean
        @ConditionalOnClass(RedisOperations.class)
        @ConditionalOnBean(StringRedisTemplate.class)
        public JWT.Clock clock(StringRedisTemplate template) {
            return template::getCurrentTime;
        }
    }

    @Configuration
    @Component
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    static class MvcConfiguration implements WebMvcConfigurer {

        @Bean
        public DefaultResponseBodyConverter defaultResponseBodyConverter() {
            return new DefaultResponseBodyConverter();
        }

        @Bean
        public LogFilter logFilter() {
            return new LogFilter();
        }

        @Bean
        public HeaderFilter headerProcessorFilter() {
            return new HeaderFilter();
        }

        @Bean
        @ConditionalOnProperty(prefix = "app.api.finder", name = "enable", havingValue = "true")
        public ApiPoster deprecatedApiPoster() {
            return new ApiPoster() {
                @Override
                public void handlerUrl(String url) {
                    RedisService redisService = SpringApplicationContext.getBean(RedisService.class);
                    if (redisService != null) {
                        redisService.putValueToMap("api:url", url, DateUtil.getToday());
                    }
                }

                @Override
                public void handlerApis(Set<ApiFinder.Api> apis) {
                    RedisService redisService = SpringApplicationContext.getBean(RedisService.class);
                    if (redisService != null) {
                        redisService.putObject("api:mappings", apis.stream()
                                .map(api -> api.getMethod() + "-" + api.getPattern()).collect(Collectors.toList()));
                    }
                }
            };
        }

        @Bean
        @ConditionalOnProperty(prefix = "app.api.finder", name = "enable", havingValue = "true")
        public ApiFinder deprecatedApiFinder() {
            return new ApiFinder();
        }

        @Bean
        public XssFilter xssFilter() {
            return new XssFilter();
        }

        @Bean
        @ConditionalOnBean(MutableHeaderCallback.class)
        public MutableHeaderFilter mutableHeaderFilter(MutableHeaderCallback callback) {
            return MutableHeaderFilter.builder().callback(callback).build();
        }

        @Bean
        public ApiInterceptor apiInterceptor() {
            return new ApiInterceptor();
        }

        @Bean
        public GsonHttpMessageConverter gsonHttpMessageConverter() {
            return new GsonHttpMessageConverter();
        }

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            addApiInterceptor(registry);
            addLogInterceptor(registry);
            addHeaderInterceptor(registry);
        }

        private void addApiInterceptor(InterceptorRegistry registry) {
            InterceptorRegistration ir = registry.addInterceptor(apiInterceptor());
            ir.addPathPatterns("/**");
            ir.order(Ordered.HIGHEST_PRECEDENCE);
        }

        private void addLogInterceptor(InterceptorRegistry registry) {
            InterceptorRegistration ir = registry.addInterceptor(logFilter());
            ir.addPathPatterns("/**");
            ir.order(Ordered.HIGHEST_PRECEDENCE + 10);
        }

        private void addHeaderInterceptor(InterceptorRegistry registry) {
            InterceptorRegistration ir = registry.addInterceptor(headerProcessorFilter());
            ir.addPathPatterns("/**");
            ir.order(Ordered.HIGHEST_PRECEDENCE + 100);
        }

    }

}
